package simulation;

import java.util.ArrayList;
import java.util.Random;

import utilities.Constants;
import animation.AnimatedMap;
import dataobjects.Direction;
import dataobjects.Edge;
import dataobjects.Position;
import exceptions.DestroyedException;
import exceptions.ImpossibleException;

public class SimulatedGridReading {

	private ArrayList<Edge> obstacleList = new ArrayList<Edge>();
	private ArrayList<Edge> iedList = new ArrayList<Edge>();
	private AnimatedMap animatedMap;
	private ArrayList<Edge> edges = new ArrayList<Edge> ();

	Random generator = new Random();

	public SimulatedGridReading(AnimatedMap animatedMap){
		this.animatedMap = animatedMap;
	}

	public boolean checkGridBounds(Position pos){
		
		if (pos.getX() < 0 || pos.getX() > Constants.GRID_WIDTH
				|| pos.getY() < 0 || pos.getY() > Constants.GRID_HEIGHT){
			return false;
		}
		return true;
	}

	public boolean checkPathOpen(){
		ArrayList<Position> posDeque = new ArrayList<Position>();
		posDeque.add(new Position(0, 0));
		Direction[] dirs = new Direction[2];
		dirs[0] = Direction.NORTH; //why are we not checking south and west? Doesn't this fail if the only path is serpentine?
		dirs[1] = Direction.EAST; 
		while (!posDeque.isEmpty()){
			Position current = posDeque.remove(posDeque.size() - 1);
			for (Direction dir : dirs){
				int nextX = current.getX() + dir.getHoriz();
				int nextY = current.getY() + dir.getVert();
				Position next = new Position(nextX, nextY);
				if (checkGridBounds(next) && (!posDeque.contains(next))){
					if (!obstacleList.contains(next) && (!iedList.contains(next))){
						if (nextX == Constants.GRID_WIDTH && nextY == Constants.GRID_HEIGHT){
							return true;
						}
						posDeque.add(new Position(nextX, nextY));
					}
				}
			}
		}
		return false;
	}

	private void populateObstacles(){
		obstacleList.clear();
		ArrayList<Edge> availableEdges = new ArrayList<Edge>();
		availableEdges.addAll(edges);
		availableEdges.removeAll(iedList);
		
		for (int i = 0; i < Constants.MAX_OBSTACLES; i++){
			int edge = generator.nextInt(availableEdges.size());
			Edge obstacle = availableEdges.remove(edge); //will remove the object at index edge, not the # matching edge.
			obstacleList.add(obstacle);
			animatedMap.addObstacle(obstacle);
		}
	}

	private void populateIeds(){
		iedList.clear();
		ArrayList<Edge> availableEdges = new ArrayList<Edge>();
		availableEdges.addAll(edges);
		availableEdges.removeAll(obstacleList);
		
		for (int i = 0; i < Constants.MAX_IEDS; i++){
			int edge = generator.nextInt(availableEdges.size());
			Edge ied = availableEdges.remove(edge); //will remove the object at index edge, not the # matching edge.
			iedList.add(ied);
			animatedMap.addIed(ied);
		}
	}

	public void setup() throws ImpossibleException{
		edges.clear();
		//horizontal edges
		for (int x = 0; x < Constants.GRID_WIDTH-1; x++){
			for (int y = 0; y < Constants.GRID_HEIGHT; y++){
				Position pos0 = new Position(x, y);
				Position pos1 = new Position(x+1, y);
				edges.add(new Edge(pos0, pos1));
			}
		}
		//vertical edges
		for (int x = 0; x < Constants.GRID_WIDTH; x++){
			for (int y = 0; y < Constants.GRID_HEIGHT-1; y++){
				Position pos0 = new Position(x, y);
				Position pos1 = new Position(x, y+1);
				edges.add(new Edge(pos0, pos1));
			}
		}
		
		populateObstacles();
		populateIeds();
		if (!checkPathOpen()){
			setup(); //try again
		}
	}
	
	public void setup(ArrayList<Edge> obstacles, ArrayList<Edge> ieds) {
		edges.clear();
		//horizontal edges
		for (int x = 0; x < Constants.GRID_WIDTH-1; x++){
			for (int y = 0; y < Constants.GRID_HEIGHT; y++){
				Position pos0 = new Position(x, y);
				Position pos1 = new Position(x+1, y);
				edges.add(new Edge(pos0, pos1));
			}
		}
		//vertical edges
		for (int x = 0; x < Constants.GRID_WIDTH; x++){
			for (int y = 0; y < Constants.GRID_HEIGHT-1; y++){
				Position pos0 = new Position(x, y);
				Position pos1 = new Position(x, y+1);
				edges.add(new Edge(pos0, pos1));
			}
		}
		obstacleList.clear();
		iedList.clear();
		
		obstacleList.addAll(obstacles);
		iedList.addAll(ieds);

	}

	public boolean detectObstruction(Edge edge){
		if (obstacleList.contains(edge)){
			return true; 
		}
		if (iedList.contains(edge)){
			if(generator.nextFloat() < Constants.DETECT_RATE){
				return true; 
			}
		}
		return false;
	}

	public void move(Edge edge) throws DestroyedException{
		if (obstacleList.contains(edge)){
			throw new DestroyedException();
		}
		if (iedList.contains(edge)){
			iedList.remove(iedList.indexOf(edge));
			throw new DestroyedException();
		}
	}
	
	public void move(Position current, Position goal) throws DestroyedException{
		move(new Edge(current, goal));
	}

}
